import gymnasium as gym
#---#
import numpy as np
import collections
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import IPython
1. imports
2. q_table
q[s1,s2,a]
는 상태 (s1,s2) 에서 행동 a의 “품질”을 의미한다.
-
직관적으로 푸는 방법 (손으로 푸는 다이나믹 프로그래밍)
A. 미래보상
-
언뜻생각하면 4x4 문제에서 q_table
은 아래와 같이 생각하는게 합리적인듯 보인다.
q[s1,s2,a]
= 상태 (s1,s2)에서 행동 a를 했을 경우 얻게되는 보상의 평균- \(q(s,a) = r(s,a) = \mathbb{E}[\text{Reward} | \text{State}=s, \text{Action}=a]\)
그렇지만 아래와 같이 생각하는게 더 합리적이다.
q[s1,s2,a]
= 상태 (s1,s2)에서 행동 a를 했을 경우 얻게되는 보상의 평균 + 미래에 얻게되리라 기대하는 보상- \(q(s,a) = r(s,a) + r_{\text{future}}\)
단, 여기에서 미래에 얻게되리라 기대하는 보상은 최선의 선택을 한다는 전제하에 계산
-
미래에 얻게되리라 기대하는 보상은 어떻게 정의할 수 있을까?
# 예제1
– 상태 (2,2) 에서 “action=down” 을 했을때
- 즉시 얻게되는 보상과
- 미래에 얻게되리라 기대하는 보상
은 무엇인가? 이것을 바탕으로 \(s=(2,1)\), \(a=\text{down}\) 의 품질(Quality)는 어떻게 평가할 수 있는가?
(풀이?)
즉시 얻을 수 있다고 생각되는 보상은-1 이고 미래에 얻으리라 기대되는 보상은 100 점이다. 따라서 99점으로 평가하는게 합리적인듯하다. 수식으로 쓰면
\[q(s,a)=q(s_1,s_2,a)=q(2,2, \text{down}) = -1 + 100 = r(2,2,\text{down}) + \max_{a'}q(3,2,a')\]
와 같이 쓸 수 있겠다.
#
# 예제2
– 상태 (1,2) 에서 “action=down” 을 했을때
- 즉시 얻게되는 보상과
- 미래에 얻게되리라 기대하는 보상
은 무엇인가? 이것을 바탕으로 \(s=(1,2)\), \(a=\text{down}\) 의 품질(Quality)는 어떻게 평가할 수 있는가?
(풀이?)
즉시 얻을 수 있다고 생각되는 보상은-1 이고 미래에 얻으리라 기대되는 보상은 99점이다. 따라서 98점으로 평가하는게 합리적인듯하다. 수식으로 쓰면
\[q(s,a)=q(s_1,s_2,a)=q(1,2, \text{down}) = -1 + 99 = r(1,2,\text{down}) + \max_{a'}q(2,2,a')\]
와 같이 쓸 수 있겠다.
#
# 예제3
– 상태 (0,1) 에서 “action=right” 을 했을때
- 즉시 얻게되는 보상과
- 미래에 얻게되리라 기대하는 보상
은 무엇인가? 이것을 바탕으로 \(s=(0,1)\), \(a=\text{right}\) 의 품질(Quality)는 어떻게 평가할 수 있는가?
(풀이?)
앞의 예제들을 일반화하면 아래와 같은 수식을 쓸 수 있다.
\[q(0,1, \text{right}) = r(0,1,\text{right}) + \max_{a'}q(0,2,a')\]
따라서 만약에 \(\max_{a}q(0,2,a)\)의 값을 알고 있다면 이를 구할 수 있다.
#
-
(아직 부족한) 깨달음: 모든 \((s,a)\)에 대하여 \(q(s,a)\)의 값은 아래와 같이 정의할 수 있겠다.
\[q(s,a) = r(s,a) + \max_{a'}q(s',a')\]
B. 감가율
# 예제1
– 당신은 지금 아무것도 쓰여 있지 않은 빈 종이 한 장을 가지고 있습니다. 이 종이에 쓸 수 있는 숫자는 오직 두 가지, 0 또는 1입니다. 어떤 숫자를 쓰느냐에 따라 보상이 달라지는데, 수많은 실험을 통해 0을 쓰면 아무 보상도 없고, 1을 쓰면 10만 원을 받을 수 있다는 사실이 확인되었습니다. 이 사실이 확인된 이후 이 빈 종이의 가치는 얼마일까요?
(1)
0원이다.
(2)
10만원이다.
(3)
5만원이다.
(4)
모르겠다.
#
# 예제2
– 당신 앞에는 빨간색 종이 한 장이 있습니다. 이 종이에는 0 또는 1 중 하나의 숫자를 쓸 수 있습니다. 만약 1을 쓰면 다음 단계인 주황색 종이 한 장을 받게 됩니다. 주황색 종이에도 똑같이 0 또는 1을 쓸 수 있고, 여기에 1을 쓰면 노란색 종이, 그다음은 초록색 종이, 그 다음은 파란색 종이, 그 다음은 남색 종이, 마지막으로는 보라색 종이를 순서대로 받습니다. 총 7단계(빨강 → 주황 → 노랑 → 초록 → 파랑 → 남색 → 보라색)를 거친 후, 보라색 종이에 1을 쓰면 비로소 10만 원의 현금 보상을 받을 수 있습니다. 단, 어느 단계에서든 0을 쓰면 아무 일도 일어나지 않고 그 즉시 게임이 종료됩니다. 즉, 그 이후로는 종이도 받을 수 없고 보상도 없습니다. 이 사실이 알려진 이후, 지금 당신이 들고 있는 ’빨간색 종이’의 가치는 얼마일까요?
(1)
0원이다.
(2)
10만 원이다.
(3)
\(\frac{1}{2^6}\) x 10만원이다.
(4)
모르겠다.
#
-
직관: 아무리 보장된 보상이라고 해도, 미래에 주어지는 보상은 현재의 보상과 동급취급할 수 없다.
-
진짜 깨달음: 모든 \((s,a)\)에 대하여 \(q(s,a)\)의 값은 아래와 같이 정의하는게 합리적이다.
\[q(s,a) = r(s,a) + \gamma \max_{a'}q(s',a')\]
여기에서 \(\gamma\)는 0과 1사이의 값이며 감가율(discout factor)이라 부른다.
C. q_table
update
-
지난시간코드
class GridWorld:
def __init__(self):
self.a2d = {
0: np.array([0,1]), # →
1: np.array([0,-1]), # ←
2: np.array([1,0]), # ↓
3: np.array([-1,0]) # ↑
}self.state_space = gym.spaces.MultiDiscrete([4,4])
self.state = np.array([0,0])
self.reward = None
self.terminated = False
def step(self,action):
self.state = self.state + self.a2d[action]
= self.state
s1,s2 if (s1==3) and (s2==3):
self.reward = 100
self.terminated = True
elif self.state in self.state_space:
self.reward = -1
self.terminated = False
else:
self.reward = -10
self.terminated = True
# print(
# f"action = {action}\t"
# f"state = {self.state - self.a2d[action]} -> {self.state}\t"
# f"reward = {self.reward}\t"
# f"termiated = {self.terminated}"
# )
return self.state, self.reward, self.terminated
def reset(self):
self.state = np.array([0,0])
self.terminated = False
return self.state
class RandomAgent:
def __init__(self):
self.state = np.array([0,0])
self.action = None
self.reward = None
self.next_state = None
self.terminated = None
#---#
self.states = collections.deque(maxlen=500000)
self.actions = collections.deque(maxlen=500000)
self.rewards = collections.deque(maxlen=500000)
self.next_states = collections.deque(maxlen=500000)
self.terminations = collections.deque(maxlen=500000)
#---#
self.action_space = gym.spaces.Discrete(4)
self.n_experience = 0
def act(self):
self.action = self.action_space.sample()
def save_experience(self):
self.states.append(self.state)
self.actions.append(self.action)
self.rewards.append(self.reward)
self.next_states.append(self.next_state)
self.terminations.append(self.terminated)
self.n_experience = self.n_experience + 1
def learn(self):
pass
= RandomAgent()
player = GridWorld()
env = []
scores = 0
score #
for e in range(1,100000):
#---에피소드시작---#
while True:
# step1 -- 액션선택
player.act()# step2 -- 환경반응
= env.step(player.action)
player.next_state, player.reward, player.terminated # step3 -- 경험기록 & 학습
player.save_experience()
player.learn()# step4 --종료 조건 체크 & 후속 처리
if env.terminated:
= score + player.reward
score
scores.append(score)= 0
score = env.reset()
player.state break
else:
= score + player.reward
score = player.next_state player.state
-
상황: player가 경험은 있는데, q_table을 만들줄 모름 (데이터는 있음, 학습이 안된상태)
player.n_experience
325309
-
저번시간에 배운 q_table
= np.zeros((4,4,4))
q_table = zip(player.states, player.actions, player.rewards)
memory for (s1,s2), a, r in memory:
= q_table[s1,s2,a] # 내가 생각했던갓
qhat = r # 실제값
q = q-qhat # 차이
diff = q_table[s1,s2,a] + 0.01*diff# update q_table[s1,s2,a]
for a in [0,1,2,3]:
print(f"action = {a}")
print(f"q[...,{a}] = {q_table[...,a].round(3)}")
print("---")
action = 0
q[...,0] = [[ -1. -1. -1. -10. ]
[ -1. -1. -1. -10. ]
[ -1. -1. -1. -9.999]
[ -1. -1. 99.993 0. ]]
---
action = 1
q[...,1] = [[-10. -1. -1. -1.]
[-10. -1. -1. -1.]
[-10. -1. -1. -1.]
[-10. -1. -1. 0.]]
---
action = 2
q[...,2] = [[ -1. -1. -1. -1. ]
[ -1. -1. -1. -1. ]
[ -1. -1. -1. 99.991]
[-10. -10. -9.999 0. ]]
---
action = 3
q[...,3] = [[-10. -10. -10. -10.]
[ -1. -1. -1. -1.]
[ -1. -1. -1. -1.]
[ -1. -1. -1. 0.]]
---
-
이번시간에 배운 q_table
: 아래를 이용한다.
\[q(s,a) = r(s,a) + \gamma \max_{a'}q(s',a')\]
Note
사실 좀 더 실용적으로는(=코딩친화적으로는) 아래의 수식을 쓰는게 좋다.
\[q(s,a) = \begin{cases} r(s,a) & \text{if terminated} \\ r(s,a) + \gamma \max_{a'}q(s',a') & \text{else} \end{cases}\]
= np.zeros((4,4,4))
q_table = zip(player.states, player.actions, player.rewards, player.next_states, player.terminations)
memory for (s1,s2), a, r, (ss1,ss2), tmd in memory:
= q_table[s1,s2,a] # 내가 생각했던값
qhat if tmd:
= r # 실제값
q else:
= q_table[ss1,ss2,:].max()
future = r + 0.95 * future
q = q-qhat # 차이
diff = q_table[s1,s2,a] + 0.01*diff# update q_table[s1,s2,a]
for a in [0,1,2,3]:
print(f"action = {a}")
print(f"q[...,{a}] = {q_table[...,a].round(3)}")
print("---")
action = 0
q[...,0] = [[ 72.837 77.719 82.751 -10. ]
[ 77.724 82.869 88.26 -10. ]
[ 82.866 88.285 93.986 -9.999]
[ 88.233 93.981 99.993 0. ]]
---
action = 1
q[...,1] = [[-10. 68.193 72.83 77.663]
[-10. 72.834 77.718 82.84 ]
[-10. 77.716 82.861 88.155]
[-10. 82.717 88.132 0. ]]
---
action = 2
q[...,2] = [[ 72.837 77.724 82.864 88.191]
[ 77.72 82.869 88.285 93.97 ]
[ 82.801 88.274 93.989 99.991]
[-10. -10. -9.999 0. ]]
---
action = 3
q[...,3] = [[-10. -10. -10. -10. ]
[ 68.193 72.834 77.713 82.601]
[ 72.831 77.718 82.858 88.051]
[ 77.679 82.839 88.196 0. ]]
---
= q_table player.q_table
def act(player,s1,s2):
= player.q_table[s1,s2,:].argmax()
action return action
0,0) act(player,
2
0,0,:] player.q_table[
array([ 72.83682203, -10. , 72.83698221, -10. ])
4. Solve
act(player,)
Appendix B - 신경망관련용어
-
은근히 용어가 헷갈리는데, 뜻을 좀 살펴보자.
- ANN: 인공신경망
- MLP: 다층퍼셉트론 (레이어가 여러개 있어요)
- DNN: 깊은신경망, 심층신경망
- CNN: 합성곱신경망
- RNN: 순환신경망
# 예시1
– MLP, DNN
= torch.nn.Sequential(
net =1,out_features=2),
torch.nn.Linear(in_features
torch.nn.ReLU(),=2,out_features=2),
torch.nn.Linear(in_features
torch.nn.ReLU(),=2,out_features=1),
torch.nn.Linear(in_features
torch.nn.Sigmoid() )
- ANN: O
- MLP: O
- DNN: O
- CNN: X (합성곱레이어가 없으므로)
- RNN: X (순환구조가 없으므로)
#
# 예시2
– MLP, Shallow Network
= torch.nn.Sequential(
net =1,out_features=2),
torch.nn.Linear(in_features
torch.nn.ReLU(),=2,out_features=1),
torch.nn.Linear(in_features
torch.nn.Sigmoid() )
- ANN: O
- MLP: O
- DNN: X (깊은 신경망으로 생각하려면 더 많은 레이어가 필요함. 합의된 기준은 히든레이어 2장이상, 이걸 설명하기 위해서 얕은 신경망이란 용어도 씀)
- CNN: X (합성곱레이어가 없으므로)
- RNN: X (순환구조가 없으므로)
#
# 예시3
– MLP, DNN, Wide NN
= torch.nn.Sequential(
net =1,out_features=1048576),
torch.nn.Linear(in_features
torch.nn.ReLU(),=1048576,out_features=1048576),
torch.nn.Linear(in_features
torch.nn.ReLU(),=1048576,out_features=1),
torch.nn.Linear(in_features
torch.nn.Sigmoid(), )
- ANN: O
- MLP: O
- DNN: O (깊긴한데 이정도면 모양이 깊다기 보다는 넓은 신경망임, 그래서 어떤 연구에서는 이걸 넓은 신경망이라 부르기도 함)
- CNN: X (합성곱레이어가 없으므로)
- RNN: X (순환구조가 없으므로)
# 예시4
– CNN
= torch.nn.Sequential(
net # Layer1
1, 64, kernel_size=4, stride=2, padding=1, bias=False),
torch.nn.Conv2d(0.2),
torch.nn.LeakyReLU(# Layer2
64, 128, kernel_size=4, stride=2, padding=1, bias=False),
torch.nn.Conv2d(128),
torch.nn.BatchNorm2d(0.2),
torch.nn.LeakyReLU(# Layer3
128, 256, kernel_size=4, stride=2, padding=1, bias=False),
torch.nn.Conv2d(256),
torch.nn.BatchNorm2d(0.2),
torch.nn.LeakyReLU(# Layer4
256, 512, kernel_size=4, stride=2, padding=1, bias=False),
torch.nn.Conv2d(512),
torch.nn.BatchNorm2d(0.2),
torch.nn.LeakyReLU(# Layer5
512, 1, kernel_size=4, stride=1, padding=0, bias=False),
torch.nn.Conv2d(
torch.nn.Sigmoid(),
torch.nn.Flatten() )
- ANN: O
- MLP: X (합성곱연결이 포함되어있으므로, MLP가 아님, 완전연결만 포함해야 MLP임)
- DNN: O
- CNN: O (합성곱레이어를 포함하고 있으므로)
- RNN: X (순환구조가 없으므로)
#
# 예시5
– CNN
= torch.nn.Sequential(
net 1,16,(5,5)),
torch.nn.Conv2d(
torch.nn.ReLU(),2,2)),
torch.nn.MaxPool2d((
torch.nn.Flatten(),2304,1),
torch.nn.Linear(
torch.nn.Sigmoid() )
- ANN: O
- MLP: X
- DNN: X? (히든레이어가 1장이므로..)
- CNN: O (합성곱레이어를 포함하고 있으므로)
- RNN: X (순환구조가 없으므로)
근데 대부분의 문서에서는 CNN, RNN은 DNN의 한 종류로 설명하고 있어서요.. 이런 네트워크에서는 개념충돌이 옵니다.
#
# 예시6
– RNN
class Net(torch.nn.Module):
def __init__(self):
super().__init__()
self.rnn = torch.nn.RNN(4,2)
self.linr = torch.nn.Linear(2,2)
def forward(self,X):
= self.rnn(X)
h,_ = self.linr(h)
netout return netout
= Net() net
- ANN: O
- MLP: X
- DNN: X? (히든레이어가 1장이므로..)
- CNN: X (합성곱레이어가 없으므로)
- RNN: O
이것도 비슷한 개념충돌